]> git.neil.brown.name Git - wiggle.git/blob - wiggle.c
diff: trim endpoints of search more agressively.
[wiggle.git] / wiggle.c
1 /*
2  * wiggle - apply rejected patches
3  *
4  * Copyright (C) 2003 Neil Brown <neilb@cse.unsw.edu.au>
5  * Copyright (C) 2010-2013 Neil Brown <neilb@suse.de>
6  *
7  *
8  *    This program is free software; you can redistribute it and/or modify
9  *    it under the terms of the GNU General Public License as published by
10  *    the Free Software Foundation; either version 2 of the License, or
11  *    (at your option) any later version.
12  *
13  *    This program is distributed in the hope that it will be useful,
14  *    but WITHOUT ANY WARRANTY; without even the implied warranty of
15  *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  *    GNU General Public License for more details.
17  *
18  *    You should have received a copy of the GNU General Public License
19  *    along with this program.
20  *
21  *    Author: Neil Brown
22  *    Email: <neilb@suse.de>
23  */
24
25 /*
26  * Wiggle is a tool for working with patches that don't quite apply properly.
27  * It provides functionality similar to 'diff' and 'merge' but can
28  * work at the level of individual words thus allowing the merging of
29  * two changes that affect the same line, but not the same parts of that line.
30  *
31  * Wiggle can also read patch and merge files.  Unlike 'merge' it does not
32  * need to be given three separate files, but can be given a file and a patch
33  * and it will extract the pieces of the two other files that it needs from
34  * the patch.
35  *
36  * Wiggle performs one of three core function:
37  *   --extract -x    extract part of a patch or merge file
38  *   --diff -d       report differences between two files
39  *   --merge -m      merge the changes between two files into a third file
40  *
41  * This is also a --browse (-B) mode which provides interactive access
42  * to the merger.
43  *
44  * To perform these, wiggle requires 1, 2, or 3 input streams respectively.
45  * I can get these from individual files, from a diff (unified or context) or
46  * from a merge file.
47  *
48  * For merge:
49  *    If one file is given, it is a merge file (output of 'merge').
50  *    If two files are given, the second is assumed to be a patch,
51  *         the first is a normal file.
52  *    If three files are given, they are taken to be normal files.
53  *
54  * For diff:
55  *    If one file is given, it is a patch
56  *    If two files are given, they are normal files.
57  *
58  * For extract:
59  *    Only one file can be given. -p indicates it is a patch,
60  *        otherwise it is a merge.
61  *    One of the flags -1 -2 or -3 must also be given and they indicate which
62  *    part of the patch or merge to extract.
63  *
64  * Difference calculation and merging is performed on lines (-l) or words (-w).
65  * Each 'word' is either 1/all alphnumeric (or '_'), 2/ all space or tab,
66  * or 3/ any other single character.
67  *
68  * In the case of -w, an initial diff is computed based on non-trivial words
69  * which includes alhpanumeric words and newlines.
70  *
71  * This diff is computed from the ends of the file and is used to find
72  * a suitable starting point and range.  Then a more precise diff is
73  * computed over that restricted range
74  *
75  * Other options available are:
76  *   --replace -r   replace first file with  result of merge.
77  *   --help -h      provide help
78  *   --version -v   version
79  *
80  * Defaults are --merge --words
81  *
82  */
83 #define _GNU_SOURCE
84 #include        "wiggle.h"
85 #include        <errno.h>
86 #include        <fcntl.h>
87 #include        <unistd.h>
88 #include        <stdlib.h>
89 #include        <stdio.h>
90 #include        <ctype.h>
91 #include        <sys/stat.h>
92
93 char *Cmd = "wiggle";
94 int do_trace = 0;
95
96 void die(char *reason)
97 {
98         fprintf(stderr, "%s: fatal error: %s failure\n", Cmd, reason);
99         exit(3);
100 }
101
102 void check_dir(char *name, int fd)
103 {
104         struct stat st;
105         if (fstat(fd, &st) != 0) {
106                 fprintf(stderr, "%s: fatal: %s is strange\n", Cmd, name);
107                 exit(3);
108         }
109         if (S_ISDIR(st.st_mode)) {
110                 fprintf(stderr, "%s: %s is a directory\n", Cmd, name);
111                 exit(3);
112         }
113 }
114
115 void *xmalloc(int size)
116 {
117         void *rv = malloc(size);
118         if (size && !rv) {
119                 char *msg = "Failed to allocate memory - aborting\n";
120                 write(2, msg, strlen(msg));
121                 exit(3);
122         }
123         return rv;
124 }
125
126 void printword(FILE *f, struct elmnt e)
127 {
128         if (e.start[0])
129                 fprintf(f, "%.*s", e.plen + e.prefix,
130                         e.start - e.prefix);
131         else {
132                 int a, b, c;
133                 sscanf(e.start+1, "%d %d %d", &a, &b, &c);
134                 fprintf(f, "*** %d,%d **** %d%s", b, c, a, e.start+18);
135         }
136 }
137
138 static void printsep(struct elmnt e1, struct elmnt e2)
139 {
140         int a, b, c, d, e, f;
141         sscanf(e1.start+1, "%d %d %d", &a, &b, &c);
142         sscanf(e2.start+1, "%d %d %d", &d, &e, &f);
143         printf("@@ -%d,%d +%d,%d @@%s", b, c, e, f, e1.start+18);
144 }
145
146 static int extract(int argc, char *argv[], int ispatch, int which)
147 {
148         /* extract a branch of a diff or diff3 or merge output
149          * We need one file
150          */
151         struct stream f, flist[3];
152
153         if (argc == 0) {
154                 fprintf(stderr,
155                         "%s: no file given for --extract\n", Cmd);
156                 return 2;
157         }
158         if (argc > 1) {
159                 fprintf(stderr,
160                         "%s: only give one file for --extract\n", Cmd);
161                 return 2;
162         }
163         f = load_file(argv[0]);
164         if (f.body == NULL) {
165                 fprintf(stderr,
166                         "%s: cannot load file '%s' - %s\n", Cmd,
167                         argv[0], strerror(errno));
168                 return 2;
169         }
170         if (ispatch) {
171                 if (split_patch(f, &flist[0], &flist[1]) == 0) {
172                         fprintf(stderr,
173                                 "%s: No chunk found in patch: %s\n", Cmd,
174                                 argv[0]);
175                         return 0;
176                 }
177         } else {
178                 if (!split_merge(f, &flist[0], &flist[1], &flist[2])) {
179                         fprintf(stderr,
180                                 "%s: merge file %s looks bad.\n", Cmd,
181                                 argv[0]);
182                         return 2;
183                 }
184         }
185         if (flist[which-'1'].body == NULL) {
186                 fprintf(stderr,
187                         "%s: %s has no -%c component.\n", Cmd,
188                         argv[0], which);
189                 return 2;
190         } else {
191                 if (write(1, flist[which-'1'].body,
192                           flist[which-'1'].len)
193                     != flist[which-'1'].len)
194                         return 2;
195         }
196         return 0;
197 }
198
199 static int do_diff_lines(struct file fl[2], struct csl *csl)
200 {
201         int a, b;
202         int exit_status = 0;
203         a = b = 0;
204         while (a < fl[0].elcnt || b < fl[1].elcnt) {
205                 if (a < csl->a) {
206                         if (fl[0].list[a].start[0]) {
207                                 printf("-");
208                                 printword(stdout,
209                                           fl[0].list[a]);
210                         }
211                         a++;
212                         exit_status++;
213                 } else if (b < csl->b) {
214                         if (fl[1].list[b].start[0]) {
215                                 printf("+");
216                                 printword(stdout,
217                                           fl[1].list[b]);
218                         }
219                         b++;
220                         exit_status++;
221                 } else {
222                         if (fl[0].list[a].start[0] == '\0')
223                                 printsep(fl[0].list[a],
224                                          fl[1].list[b]);
225                         else {
226                                 printf(" ");
227                                 printword(stdout,
228                                           fl[0].list[a]);
229                         }
230                         a++;
231                         b++;
232                         if (a >= csl->a+csl->len)
233                                 csl++;
234                 }
235         }
236         return exit_status;
237 }
238
239 static int do_diff_words(struct file fl[2], struct csl *csl)
240 {
241         int a, b;
242         int exit_status  = 0;
243         int sol = 1; /* start of line */
244         a = b = 0;
245         while (a < fl[0].elcnt || b < fl[1].elcnt) {
246                 if (a < csl->a) {
247                         exit_status++;
248                         if (sol) {
249                                 int a1;
250                                 /* If we remove a
251                                  * whole line, output
252                                  * +line else clear
253                                  * sol and retry */
254                                 sol = 0;
255                                 for (a1 = a; a1 < csl->a ; a1++)
256                                         if (ends_line(fl[0].list[a1])) {
257                                                 sol = 1;
258                                                 break;
259                                         }
260                                 if (sol) {
261                                         printf("-");
262                                         for (; a < csl->a ; a++) {
263                                                 printword(stdout, fl[0].list[a]);
264                                                 if (ends_line(fl[0].list[a])) {
265                                                         a++;
266                                                         break;
267                                                 }
268                                         }
269                                 } else
270                                         printf("|");
271                         }
272                         if (!sol) {
273                                 printf("<<<--");
274                                 do {
275                                         if (sol)
276                                                 printf("|");
277                                         printword(stdout, fl[0].list[a]);
278                                         sol = ends_line(fl[0].list[a]);
279                                         a++;
280                                 } while (a < csl->a);
281                                 printf("%s-->>>", sol ? "|" : "");
282                                 sol = 0;
283                         }
284                 } else if (b < csl->b) {
285                         exit_status++;
286                         if (sol) {
287                                 int b1;
288                                 sol = 0;
289                                 for (b1 = b; b1 < csl->b; b1++)
290                                         if (ends_line(fl[1].list[b1])) {
291                                                 sol = 1;
292                                                 break;
293                                         }
294                                 if (sol) {
295                                         printf("+");
296                                         for (; b < csl->b ; b++) {
297                                                 printword(stdout, fl[1].list[b]);
298                                                 if (ends_line(fl[1].list[b])) {
299                                                         b++;
300                                                         break;
301                                                 }
302                                         }
303                                 } else
304                                         printf("|");
305                         }
306                         if (!sol) {
307                                 printf("<<<++");
308                                 do {
309                                         if (sol)
310                                                 printf("|");
311                                         printword(stdout, fl[1].list[b]);
312                                         sol = ends_line(fl[1].list[b]);
313                                         b++;
314                                 } while (b < csl->b);
315                                 printf("%s++>>>", sol ? "|" : "");
316                                 sol = 0;
317                         }
318                 } else {
319                         if (sol) {
320                                 int a1;
321                                 sol = 0;
322                                 for (a1 = a; a1 < csl->a+csl->len; a1++)
323                                         if (ends_line(fl[0].list[a1]))
324                                                 sol = 1;
325                                 if (sol) {
326                                         if (fl[0].list[a].start[0]) {
327                                                 printf(" ");
328                                                 for (; a < csl->a+csl->len; a++, b++) {
329                                                         printword(stdout, fl[0].list[a]);
330                                                         if (ends_line(fl[0].list[a])) {
331                                                                 a++, b++;
332                                                                 break;
333                                                         }
334                                                 }
335                                         } else {
336                                                 printsep(fl[0].list[a], fl[1].list[b]);
337                                                 a++; b++;
338                                         }
339                                 } else
340                                         printf("|");
341                         }
342                         if (!sol) {
343                                 printword(stdout, fl[0].list[a]);
344                                 if (ends_line(fl[0].list[a]))
345                                         sol = 1;
346                                 a++;
347                                 b++;
348                         }
349                         if (a >= csl->a+csl->len)
350                                 csl++;
351                 }
352         }
353         return exit_status;
354 }
355
356 static int do_diff(int argc, char *argv[], int obj, int ispatch,
357                    int which, int reverse)
358 {
359         /* create a diff (line or char) of two streams */
360         struct stream f, flist[3];
361         int chunks1 = 0, chunks2 = 0, chunks3 = 0;
362         int exit_status = 0;
363         struct file fl[2];
364         struct csl *csl;
365
366         switch (argc) {
367         case 0:
368                 fprintf(stderr, "%s: no file given for --diff\n", Cmd);
369                 return 2;
370         case 1:
371                 f = load_file(argv[0]);
372                 if (f.body == NULL) {
373                         fprintf(stderr,
374                                 "%s: cannot load file '%s' - %s\n", Cmd,
375                                 argv[0], strerror(errno));
376                         return 2;
377                 }
378                 chunks1 = chunks2 =
379                         split_patch(f, &flist[0], &flist[1]);
380                 if (!flist[0].body || !flist[1].body) {
381                         fprintf(stderr,
382                                 "%s: couldn't parse patch %s\n", Cmd,
383                                 argv[0]);
384                         return 2;
385                 }
386                 break;
387         case 2:
388                 flist[0] = load_file(argv[0]);
389                 if (flist[0].body == NULL) {
390                         fprintf(stderr,
391                                 "%s: cannot load file '%s' - %s\n", Cmd,
392                                 argv[0], strerror(errno));
393                         return 2;
394                 }
395                 if (ispatch) {
396                         f = load_file(argv[1]);
397                         if (f.body == NULL) {
398                                 fprintf(stderr,
399                                         "%s: cannot load patch '%s' - %s\n", Cmd,
400                                         argv[1], strerror(errno));
401                                 return 2;
402                         }
403                         if (which == '2')
404                                 chunks2 = chunks3 =
405                                         split_patch(f, &flist[2],
406                                                     &flist[1]);
407                         else
408                                 chunks2 = chunks3 =
409                                         split_patch(f, &flist[1],
410                                                     &flist[2]);
411
412                 } else
413                         flist[1] = load_file(argv[1]);
414                 if (flist[1].body == NULL) {
415                         fprintf(stderr,
416                                 "%s: cannot load file '%s' - %s\n", Cmd,
417                                 argv[1], strerror(errno));
418                         return 2;
419                 }
420                 break;
421         default:
422                 fprintf(stderr,
423                         "%s: too many files given for --diff\n", Cmd);
424                 return 2;
425         }
426         if (reverse) {
427                 f = flist[0];
428                 flist[0] = flist[1];
429                 flist[1] = f;
430         }
431         fl[0] = split_stream(flist[0], obj);
432         fl[1] = split_stream(flist[1], obj);
433         if (!(obj & WholeWord) && fl[0].elcnt > 50000 && fl[1].elcnt > 50000) {
434                 /* Too big - use fewer words if possible */
435                 free(fl[0].list);
436                 free(fl[1].list);
437                 obj |= WholeWord;
438                 fl[0] = split_stream(flist[0], obj);
439                 fl[1] = split_stream(flist[1], obj);
440         }
441         if (chunks2 && !chunks1)
442                 csl = pdiff(fl[0], fl[1], chunks2);
443         else
444                 csl = diff_patch(fl[0], fl[1]);
445         if ((obj & ByMask) == ByLine) {
446                 if (!chunks1)
447                         printf("@@ -1,%d +1,%d @@\n",
448                                fl[0].elcnt, fl[1].elcnt);
449                 exit_status = do_diff_lines(fl, csl);
450         } else {
451                 if (!chunks1) {
452                         /* count lines in each file */
453                         int l1, l2, i;
454                         l1 = l2 = 0;
455                         for (i = 0 ; i < fl[0].elcnt ; i++)
456                                 if (ends_line(fl[0].list[i]))
457                                         l1++;
458                         for (i = 0 ; i < fl[1].elcnt ; i++)
459                                 if (ends_line(fl[1].list[i]))
460                                         l2++;
461                         printf("@@ -1,%d +1,%d @@\n", l1, l2);
462                 }
463                 exit_status = do_diff_words(fl, csl);
464         }
465         return exit_status;
466 }
467
468 static int do_merge(int argc, char *argv[], int obj, int blanks,
469                     int reverse, int replace, char *outfilename,
470                     int ignore, int show_wiggles,
471                     int quiet)
472 {
473         /* merge three files, A B C, so changed between B and C get made to A
474          */
475         struct stream f, flist[3];
476         struct file fl[3];
477         int i;
478         int chunks1 = 0, chunks2 = 0, chunks3 = 0;
479         char *replacename = NULL, *orignew = NULL;
480         struct csl *csl1, *csl2;
481         struct ci ci;
482         FILE *outfile = stdout;
483
484         switch (argc) {
485         case 0:
486                 fprintf(stderr, "%s: no files given for --merge\n", Cmd);
487                 return 2;
488         case 3:
489         case 2:
490         case 1:
491                 for (i = 0; i < argc; i++) {
492                         flist[i] = load_file(argv[i]);
493                         if (flist[i].body == NULL) {
494                                 fprintf(stderr, "%s: cannot load file '%s' - %s\n",
495                                         Cmd,
496                                         argv[i], strerror(errno));
497                                 return 2;
498                         }
499                 }
500                 break;
501         default:
502                 fprintf(stderr, "%s: too many files given for --merge\n",
503                         Cmd);
504                 return 2;
505         }
506         switch (argc) {
507         case 1: /* a merge file */
508                 f = flist[0];
509                 if (!split_merge(f, &flist[0], &flist[1], &flist[2])) {
510                         fprintf(stderr, "%s: merge file %s looks bad.\n",
511                                 Cmd,
512                                 argv[0]);
513                         return 2;
514                 }
515                 break;
516         case 2: /* a file and a patch */
517                 f = flist[1];
518                 chunks2 = chunks3 = split_patch(f, &flist[1], &flist[2]);
519                 break;
520         case 3: /* three separate files */
521                 break;
522         }
523         if (reverse) {
524                 f = flist[1];
525                 flist[1] = flist[2];
526                 flist[2] = f;
527         }
528
529         for (i = 0; i < 3; i++) {
530                 if (flist[i].body == NULL) {
531                         fprintf(stderr, "%s: file %d missing\n", Cmd, i);
532                         return 2;
533                 }
534         }
535         if (outfilename) {
536                 outfile = fopen(outfilename, "w");
537                 if (!outfile) {
538                         fprintf(stderr, "%s: could not create %s\n",
539                                 Cmd, outfilename);
540                         return 2;
541                 }
542         } else if (replace) {
543                 int fd;
544                 replacename = xmalloc(strlen(argv[0]) + 20);
545                 orignew = xmalloc(strlen(argv[0]) + 20);
546                 strcpy(replacename, argv[0]);
547                 strcpy(orignew, argv[0]);
548                 strcat(orignew, ".porig");
549                 if (open(orignew, O_RDONLY) >= 0 ||
550                     errno != ENOENT) {
551                         fprintf(stderr, "%s: %s already exists\n",
552                                 Cmd,
553                                 orignew);
554                         free(replacename);
555                         free(orignew);
556                         return 2;
557                 }
558                 strcat(replacename, "XXXXXX");
559                 fd = mkstemp(replacename);
560                 if (fd == -1) {
561                         fprintf(stderr,
562                                 "%s: could not create temporary file for %s\n",
563                                 Cmd,
564                                 replacename);
565                         free(replacename);
566                         free(orignew);
567                         return 2;
568                 }
569                 outfile = fdopen(fd, "w");
570         }
571
572         if (obj == 'l')
573                 blanks |= ByLine;
574         else
575                 blanks |= ByWord;
576         fl[0] = split_stream(flist[0], blanks);
577         fl[1] = split_stream(flist[1], blanks);
578         fl[2] = split_stream(flist[2], blanks);
579         if (!(blanks & WholeWord) &&
580             fl[1].elcnt > 50000 &&
581             (fl[0].elcnt > 50000 || fl[2].elcnt > 50000)) {
582                 /* Too many words */
583                 free(fl[0].list);
584                 free(fl[1].list);
585                 free(fl[2].list);
586                 blanks |= WholeWord;
587                 fl[0] = split_stream(flist[0], blanks);
588                 fl[1] = split_stream(flist[1], blanks);
589                 fl[2] = split_stream(flist[2], blanks);
590         }
591
592         if (chunks2 && !chunks1)
593                 csl1 = pdiff(fl[0], fl[1], chunks2);
594         else
595                 csl1 = diff(fl[0], fl[1]);
596         csl2 = diff_patch(fl[1], fl[2]);
597
598         ci = make_merger(fl[0], fl[1], fl[2], csl1, csl2,
599                          obj == 'w', ignore, show_wiggles > 1);
600         print_merge(outfile, &fl[0], &fl[1], &fl[2],
601                     obj == 'w', ci.merger, NULL, 0, 0);
602         if (!quiet && ci.conflicts)
603                 fprintf(stderr,
604                         "%d unresolved conflict%s found\n",
605                         ci.conflicts,
606                         ci.conflicts == 1 ? "" : "s");
607         if (!quiet && ci.ignored)
608                 fprintf(stderr,
609                         "%d already-applied change%s ignored\n",
610                         ci.ignored,
611                         ci.ignored == 1 ? "" : "s");
612
613         if (outfilename)
614                 fclose(outfile);
615         else if (replace) {
616                 struct stat statbuf;
617
618                 if (stat(argv[0], &statbuf) != 0) {
619                         fprintf(stderr,
620                                 "%s: failed to stat original file. - %s\n",
621                                 Cmd, strerror(errno));
622                         free(replacename);
623                         free(orignew);
624                         return 2;
625                 }
626                 if (fchmod(fileno(outfile), statbuf.st_mode) != 0) {
627                         fprintf(stderr,
628                                 "%s: failed to change permission of new file. - %s\n",
629                                 Cmd, strerror(errno));
630                         free(replacename);
631                         free(orignew);
632                         return 2;
633                 }
634                 fclose(outfile);
635                 if (rename(argv[0], orignew) == 0 &&
636                     rename(replacename, argv[0]) == 0)
637                         /* all ok */;
638                 else {
639                         fprintf(stderr,
640                                 "%s: failed to move new file into place.\n",
641                                 Cmd);
642                         free(replacename);
643                         free(orignew);
644                         return 2;
645                 }
646         }
647         free(replacename);
648         free(orignew);
649         if (show_wiggles)
650                 return ci.conflicts + ci.wiggles > 0;
651         else
652                 return ci.conflicts > 0;
653 }
654
655 static int multi_merge(int argc, char *argv[], int obj, int blanks,
656                        int reverse, int ignore, int show_wiggles,
657                        int replace, int strip,
658                        int quiet)
659 {
660         FILE *f;
661         char *filename;
662         struct plist *pl;
663         int num_patches;
664         int rv = 0;
665         int i;
666
667         if (!replace) {
668                 fprintf(stderr,
669                         "%s: -p in merge mode requires -r\n",
670                         Cmd);
671                 return 2;
672         }
673         if (argc != 1) {
674                 fprintf(stderr,
675                         "%s: -p in merge mode requires exactly one file\n",
676                         Cmd);
677                 return 2;
678         }
679         filename = argv[0];
680         f = fopen(filename, "r");
681         if (!f) {
682                 fprintf(stderr, "%s: cannot open %s\n",
683                         Cmd, filename);
684                 return 2;
685         }
686         check_dir(filename, fileno(f));
687         pl = parse_patch(f, NULL, &num_patches);
688         fclose(f);
689         if (set_prefix(pl, num_patches, strip) == 0) {
690                 fprintf(stderr, "%s: aborting\n", Cmd);
691                 return 2;
692         }
693         for (i = 0; i < num_patches; i++) {
694                 char *name;
695                 char *av[2];
696                 asprintf(&name, "_wiggle_:%d:%d:%s",
697                          pl[i].start, pl[i].end, filename);
698                 av[0] = pl[i].file;
699                 av[1] = name;
700                 rv |= do_merge(2, av, obj, blanks, reverse, 1, NULL, ignore,
701                          show_wiggles, quiet);
702         }
703         return rv;
704 }
705
706 int main(int argc, char *argv[])
707 {
708         int opt;
709         int option_index;
710         int mode = 0;
711         int obj = 0;
712         int replace = 0;
713         int backup = 1;
714         int which = 0;
715         int ispatch = 0;
716         int reverse = 0;
717         int verbose = 0, quiet = 0;
718         int strip = -1;
719         int exit_status = 0;
720         int ignore = 1;
721         int show_wiggles = 0;
722         char *helpmsg;
723         char *trace;
724         char *outfile = NULL;
725         int selftest = 0;
726         int ignore_blanks = 0;
727
728         trace = getenv("WIGGLE_TRACE");
729         if (trace && *trace)
730                 do_trace = 1;
731
732         while ((opt = getopt_long(argc, argv,
733                                   short_options, long_options,
734                                   &option_index)) != -1)
735                 switch (opt) {
736                 case 'h':
737                         helpmsg = Help;
738                         switch (mode) {
739                         case 'x':
740                                 helpmsg = HelpExtract;
741                                 break;
742                         case 'd':
743                                 helpmsg = HelpDiff;
744                                 break;
745                         case 'm':
746                                 helpmsg = HelpMerge;
747                                 break;
748                         case 'B':
749                                 helpmsg = HelpBrowse;
750                                 break;
751                         }
752                         fputs(helpmsg, stderr);
753                         exit(0);
754
755                 case 'V':
756                         fputs(Version, stderr);
757                         exit(0);
758                 case ':':
759                 case '?':
760                 default:
761                         fputs(Usage, stderr);
762                         exit(2);
763
764                 case 'B':
765                 case 'x':
766                 case 'd':
767                 case 'm':
768                         if (mode == 0) {
769                                 mode = opt;
770                                 continue;
771                         }
772                         if (mode == 'B' && opt == 'd') {
773                                 /* Browse/diff mode */
774                                 ispatch = 2;
775                                 continue;
776                         }
777                         fprintf(stderr,
778                                 "%s: mode is '%c' - cannot set to '%c'\n",
779                                 Cmd, mode, opt);
780                         exit(2);
781
782                 case NON_SPACE:
783                         ignore_blanks |= WholeWord;
784                         continue;
785
786                 case 'w':
787                 case 'l':
788                         if (obj == 0 || obj == opt) {
789                                 obj = opt;
790                                 continue;
791                         }
792                         fprintf(stderr,
793                                 "%s: cannot select both words and lines.\n", Cmd);
794                         exit(2);
795
796                 case 'r':
797                         replace = 1;
798                         continue;
799                 case NO_BACKUP:
800                         backup = 0;
801                         continue;
802                 case 'o':
803                         outfile = optarg;
804                         replace = 1;
805                         continue;
806                 case 'R':
807                         reverse = 1;
808                         continue;
809
810                 case 'b':
811                         ignore_blanks |= IgnoreBlanks;
812                         continue;
813
814                 case 'i':
815                         ignore = 0;
816                         continue;
817                 case 'W':
818                         show_wiggles = 2;
819                         ignore = 0;
820                         continue;
821                 case REPORT_WIGGLES:
822                         show_wiggles = 1;
823                         continue;
824
825                 case '1':
826                 case '2':
827                 case '3':
828                         if (which == 0 || which == opt) {
829                                 which = opt;
830                                 continue;
831                         }
832                         fprintf(stderr,
833                                 "%s: can only select one of -1, -2, -3\n", Cmd);
834                         exit(2);
835
836                 case 'p': /* 'patch' or 'strip' */
837                         if (optarg)
838                                 strip = atol(optarg);
839                         ispatch = 1;
840                         continue;
841
842                 case 'v':
843                         verbose++;
844                         continue;
845                 case 'q':
846                         quiet = 1;
847                         continue;
848
849                 case SELF_TEST:
850                         selftest = 1;
851                         continue;
852                 }
853         if (!mode)
854                 mode = 'm';
855
856         if (mode == 'B') {
857                 vpatch(argc-optind, argv+optind, ispatch,
858                        strip, reverse, replace, outfile, selftest,
859                        ignore_blanks, backup);
860                 /* should not return */
861                 exit(1);
862         }
863
864         if (obj && mode == 'x') {
865                 fprintf(stderr,
866                         "%s: cannot specify --line or --word with --extract\n",
867                         Cmd);
868                 exit(2);
869         }
870         if (mode != 'm' && !obj)
871                 obj = 'w';
872         if (ispatch && outfile) {
873                 fprintf(stderr, "%s: --output incompatible with --patch\n",
874                         Cmd);
875                 exit(2);
876         }
877         if (replace && mode != 'm') {
878                 fprintf(stderr,
879                         "%s: --replace or --output only allowed with --merge\n", Cmd);
880                 exit(2);
881         }
882         if (mode == 'x' && !which) {
883                 fprintf(stderr,
884                         "%s: must specify -1, -2 or -3 with --extract\n", Cmd);
885                 exit(2);
886         }
887         if (mode != 'x' && mode != 'd' && which) {
888                 fprintf(stderr,
889                         "%s: -1, -2 or -3 only allowed with --extract or --diff\n",
890                         Cmd);
891                 exit(2);
892         }
893
894         if (ispatch && which == '3') {
895                 fprintf(stderr,
896                         "%s: cannot extract -3 from a patch.\n", Cmd);
897                 exit(2);
898         }
899
900         switch (mode) {
901         case 'x':
902                 exit_status = extract(argc-optind, argv+optind, ispatch, which);
903                 break;
904         case 'd':
905                 exit_status = do_diff(argc-optind, argv+optind,
906                                       (obj == 'l' ? ByLine : ByWord)
907                                       | ignore_blanks,
908                                       ispatch, which, reverse);
909                 break;
910         case 'm':
911                 if (ispatch)
912                         exit_status = multi_merge(argc-optind,
913                                                   argv+optind, obj,
914                                                   ignore_blanks,
915                                                   reverse, ignore,
916                                                   show_wiggles,
917                                                   replace, strip,
918                                                   quiet);
919                 else
920                         exit_status = do_merge(
921                                 argc-optind, argv+optind,
922                                 obj, ignore_blanks, reverse, replace,
923                                 outfile,
924                                 ignore, show_wiggles, quiet);
925                 break;
926         }
927         exit(exit_status);
928 }